/*********************************************************************************************************
 *  ------------------------------------------------------------------------------------------------------
 *  file description
 *  ------------------------------------------------------------------------------------------------------
 *         \file  str.c
 *        \brief  This is a general C language string container module
 *       \author  Lamdonn
 *        \email  Lamdonn@163.com
 *      \version  1.1.0
 *      \license  GPL2
 *    \copyright  Copyright (C) 2023 Lamdonn.
 ********************************************************************************************************/
#include "vlib.h"
#include <string.h>
#include <stdio.h>
#include <math.h>

/* str identification mark */
#define IDENT                           (-1)

/* tool */
#define up_multiple(x, mul)             ((x)+((mul)-((x)-1)%(mul))-1)
#define is_digit(c)                     ((unsigned int)((c) - '0') < 10)
#define MAX(x, y)                       ((x)>(y)?(x):(y))

/* error space */
static char error = 0;

/* str info define */
typedef struct 
{
    char *base;
    int length;
} str_info_t;

static int gradient_capacity(int size)
{
    int capacity = 1 << (sizeof(int) * 8 / 2 - 1);

    if (size <= 1) return 1;

    while (capacity >= size) capacity >>= 1;

    if (capacity < 4) capacity = capacity << 1;
    else if (capacity < 16) capacity = up_multiple(size, capacity >> 1);
    else if (capacity < 256) capacity = up_multiple(size, capacity >> 2);
    else capacity = up_multiple(size, 64);
    
    return capacity;
}

/** string_info
 *  \brief get string information
 *  \param[in] string: string, type can be array string or str
 *  \return information of string
 */
static str_info_t string_info(void *string)
{
    str_info_t info;

    if (((char *)string)[0] == 0) /* empty array string */
    {
        info.base = (char *)string;
        info.length = 0;
    }
    else if (((str_t)string)->parent.ident == IDENT) /* str type */
    {
        info.base = ((str_t)string)->parent.base;
        info.length = ((str_t)string)->parent.size;
    }
    else /* array string type */
    {
        info.base = (char *)string;
        info.length = strlen((char *)string);
    }

    return info;
}

/** str_alter_capacity
 *  \brief alter the capacity of the str
 *  \param[in] str: str handler
 *  \param[in] length: length of str
 *  \return new capacity of str
 */
static int str_alter_capacity(str_t str, int length)
{
    int capacity = 0;
    char *base = NULL;

    /* check whether the capacity changes and need to adjust the capacity space */
    capacity = gradient_capacity(length);
    if (str->capacity != 0 && str->capacity == capacity) return 1;

    if (!((char *)(str->parent.base))) /* allocate new space */
    {
        str->parent.base = vlib_malloc(capacity + 1);
        if (!((char *)(str->parent.base))) return 0;
    }
    else /* reallocate space */
    {
        base = (char *)vlib_realloc(((char *)(str->parent.base)), capacity + 1);
        if (!base) return 0;

        str->parent.base = (void *)base;
    }

    str->capacity = capacity;

    return 1;
}

str_t str_create(void *string)
{
    str_t str = NULL;

    /* validity check */
    if (!string) return NULL;

    /* alloc str */
    str = (str_t)vlib_malloc(sizeof(STR));
    if (!str) return NULL;

    /* assign parameters */
    str->parent.ident = IDENT;
    str->parent.base = NULL;
    str->parent.dsize = 1;
    str->parent.size = 0;
    str->capacity = 0;

    /* assign string */
    if (!str_assign(str, string))
    {
        vlib_free(str);
        return NULL;
    }

    return str;
}

void str_delete(str_t str)
{
    /* validity check */
    if (!str) return;

    vlib_free(str->parent.base);
    vlib_free(str);
}

str_t str_assign(str_t str, void *string)
{
    str_info_t info;

    /* validity check */
    if (!str) return NULL;
    if (!string) return NULL;
    
    /* get information of sourse string */
    info = string_info(string);

    /* adjust space */
    if (!str_alter_capacity(str, info.length)) return NULL;

    /* copy string to str */
    strcpy(str->parent.base, info.base);

    /* update length of str */
    str->parent.size = info.length;

    return str;
}

char* str_data(str_t str, int pos)
{
    /* validity check */
    if (!str || pos < 0 || pos >= str->parent.size) return (char *)str_error();

    return &((char *)(str->parent.base))[pos];
}

const char* str_c_str(str_t str)
{
    return (const char *)str_data(str, 0);
}

const char* str_error()
{
    error = 0; /* reset error area */
    return &error;
}

int str_length(str_t str)
{
    /* validity check */
    if (!str) return 0;

    return str->parent.size;
}

int str_capacity(str_t str)
{
    /* validity check */
    if (!str) return 0;

    return str->capacity;
}

int str_empty(str_t str)
{
    /* validity check */
    if (!str) return 1;

    return str->parent.size == 0 ? 1 : 0;
}

void str_clear(str_t str)
{
    str_assign(str, "");
}

str_t str_insert(str_t str, int pos, void *string)
{
    return str_replace(str, pos, 0, string);
}

str_t str_erase(str_t str, int pos, int len)
{
    return str_replace(str, pos, len, "");
}

str_t str_append_series(str_t str, ...)
{
    va_list args;
    void* s = NULL;

    /* validity check */
    if (!str) return NULL;

    /* accept uncertain parameters */
    va_start(args, str);

    /* accept strings */
    s = va_arg(args, void*);
    while (s)
    {
        /* insert at the end */
        if (!str_insert(str, str->parent.size, s)) return NULL;

        s = va_arg(args, void*);
    }

    va_end(args);

    return str;
}

int str_push_back(str_t str, char c)
{
    char temp[2] = {c, 0};

    /* validity check */
    if (!str) return 0;

    if (!str_append(str, temp)) return 0;

    return 1;
}

char str_pop_back(str_t str)
{
    char c = 0;

    /* validity check */
    if (!str) return 0;

    if (str->parent.size <= 0) return 0;

    /* get char of tail */
    c = ((char *)(str->parent.base))[str->parent.size - 1];

    /* erase tail */
    if (!str_erase(str, str->parent.size - 1, 1)) return 0;

    return c;
}

int str_compare(str_t str, void *string)
{
    str_info_t info;
    int ret = 0;

    /* validity check */
    if (!str) return -1;
    if (!string) return 1;

    /* get information of sourse string */
    info = string_info(string);

    ret = strcmp(((char *)(str->parent.base)), info.base);
    if (ret > 0) ret = 1;
    else if (ret < 0) ret = -1;

    return ret;
}

str_t str_substr(str_t str, int pos, int len)
{
    str_t sub = NULL;
    int length = 0;

    /* validity check */
    if (!str) return NULL;
    if (pos < 0 || pos >= str->parent.size) return NULL;

    /* create new str */
    sub = str_create("");
    if (!sub) return NULL;

    /* calculate the actual copy length */
    length = len;
    if (length > str->parent.size - pos) length = str->parent.size - pos;

    /* adjust space */
    if (!str_alter_capacity(sub, length))
    {
        str_delete(sub);
        return NULL;
    }

    /* copy string */
    memcpy(sub->parent.base, &((char *)(str->parent.base))[pos], length);

    /* add end symbol */
    ((char *)(sub->parent.base))[length] = 0;

    /* update length of str */
    sub->parent.size = length;

    return sub;
}

int str_find(str_t str, void *string, int pos)
{
    str_info_t info;
    char *find = NULL;

    /* validity check */
    if (!str) return str_npos;
    if (!string) return str_npos;
    if (pos < 0 || pos >= str->parent.size) return str_npos;

    /* get information of sourse string */
    info = string_info(string);

    /* compare the length of the substring and the parent string */
    if (info.length > str->parent.size) return str_npos;

    /* find */
    find = strstr(&((char *)(str->parent.base))[pos], info.base);
    if (!find) return str_npos;

    /* get the position in str */
    return (int)(find - ((char *)(str->parent.base)));
}

/** strrstr
 *  \brief match substrings from the tail
 *  \param[in] haystack: parent string
 *  \param[in] tail_index: specifies the end index of the parent string character array
 *  \param[in] needle: child string
 *  \return matching segment in the parent string or NULL: fail
 */
static char* strrstr(char *haystack, int end_index, char *needle)
{
    char *find = NULL;
    int index = 0;
    int i = 0;
    int hay_len = end_index;
    int need_len = strlen(needle);

    for (index = hay_len; index >= 0; index--)
    {
        if (haystack[index] == needle[0])
        {
            find = &haystack[index];
            for (i = 0; i < need_len; i++)
            {
                if (find[i] != needle[i]) break;
            }
            if (i == need_len) return find;
        }
    }

    return NULL;
}

int str_rfind(str_t str, void *string, int pos)
{
    str_info_t info;
    char *find = NULL;

    /* validity check */
    if (!str) return str_npos;
    if (!string) return str_npos;
    if (pos < 0 || pos >= str->parent.size) return str_npos;

    /* get information of sourse string */
    info = string_info(string);

    /* compare the length of the substring and the parent string */
    if (info.length > str->parent.size) return str_npos;

    /* find */
    find = strrstr(((char *)(str->parent.base)), pos, info.base);
    if (!find) return str_npos;

    /* get the position in str */
    return (int)(find - ((char *)(str->parent.base)));
}

/** str_find_of
 *  \brief find function of "of" type
 *  \param[in] str: str handler
 *  \param[in] string: string, type can be array string or str
 *  \param[in] pos: the position to start find
 *  \param[in] from: 0: first, 1: last
 *  \param[in] of: 0: not of, 1: of
 *  \return position of the specified character or str_npos: fail
 */
static int str_find_of(str_t str, void *string, int pos, int from, int of)
{
    str_info_t info;
    int i = 0;

    /* validity check */
    if (!str) return str_npos;
    if (!string) return str_npos;
    if (pos < 0 || pos >= str->parent.size) return str_npos;

    /* get information of sourse string */
    info = string_info(string);
    if (info.length <= 0) return str_npos;

    /* matching */
    if (from == 0) /* first */
    {
        for (i = pos; i < str->parent.size; i++)
        {
            if ((of == 1 && strchr(info.base, ((char *)(str->parent.base))[i]) != NULL) ||
                (of == 0 && strchr(info.base, ((char *)(str->parent.base))[i]) == NULL))
            {
                return i;
            }
        }
    }
    else /* last */
    {
        for (i = pos; i >= 0; i--)
        {
            if ((of == 1 && strchr(info.base, ((char *)(str->parent.base))[i]) != NULL) ||
                (of == 0 && strchr(info.base, ((char *)(str->parent.base))[i]) == NULL))
            {
                return i;
            }
        }
    }

    return str_npos;
}

int str_find_first_of(str_t str, void *string, int pos)
{
    return str_find_of(str, string, pos, 0, 1);
}

int str_find_first_not_of(str_t str, void *string, int pos)
{
    return str_find_of(str, string, pos, 0, 0);
}

int str_find_last_of(str_t str, void *string, int pos)
{
    return str_find_of(str, string, pos, 1, 1);
}

int str_find_last_not_of(str_t str, void *string, int pos)
{
    return str_find_of(str, string, pos, 1, 0);
}

str_t str_reverse(str_t str, int begin, int end)
{
    int i = 0;
    char c = 0;

    /* validity check */
    if (!str) return NULL;

    if (begin < 0) begin = 0;
    if (end >= str->parent.size) end = str->parent.size - 1;
    if (begin >= end) return NULL;

    /* reverse */
    for (i = 0; i <= (end - begin) / 2; i++)
    {
        c = ((char *)(str->parent.base))[begin + i];
        ((char *)(str->parent.base))[begin + i] = ((char *)(str->parent.base))[end - i];
        ((char *)(str->parent.base))[end - i] = c;
    }

    return str;
}

str_t str_replace(str_t str, int pos, int len, void *string)
{
    str_info_t info;
    int length = 0;
    char *overlap_temp = NULL;

    /* validity check */
    if (!str) return NULL;
    if (!string) return NULL;
    if (pos < 0 || pos > str->parent.size) return NULL;

    /* calculate the actual length of be replaced */
    length = len;
    if (length > str->parent.size - pos) length = str->parent.size - pos;

    /* get information of sourse string */
    info = string_info(string);
    
    /* check if addresses overlap */
    if (((char *)(str->parent.base)) <= info.base && info.base <= ((char *)(str->parent.base)) + str->parent.size && pos < str->parent.size)
    {
        overlap_temp = (char *)vlib_malloc(info.length + 1);
        if (!overlap_temp) return NULL;

        strcpy(overlap_temp, info.base);
        info.base = overlap_temp;
    }

    if (info.length > len)
    {
        /* increase capacity first */
        if (str_alter_capacity(str, str->parent.size + (info.length - length)) == 0)
        {
            if (overlap_temp) vlib_free(overlap_temp);
            return NULL;
        }

        /* replace */
        memmove(&((char *)(str->parent.base))[pos + info.length], &((char *)(str->parent.base))[pos + length], str->parent.size - (pos + length));
        memcpy(&((char *)(str->parent.base))[pos], info.base, info.length);
    }
    else if (info.length < len)
    {
        /* replace */
        memmove(&((char *)(str->parent.base))[pos + info.length], &((char *)(str->parent.base))[pos + length], str->parent.size - (pos + length));
        memcpy(&((char *)(str->parent.base))[pos], info.base, info.length);

        /* decrease capacity after */
        str_alter_capacity(str, str->parent.size + (info.length - length));
    }
    else  
    {
        memcpy(&((char *)(str->parent.base))[pos], info.base, info.length);
    }
    
    /* update length of str */
    str->parent.size += (info.length - length);
    
    /* add end symbol */
    ((char *)(str->parent.base))[str->parent.size] = 0;

    /* release temp space */
    if (overlap_temp) vlib_free(overlap_temp);

    return str;
}

void str_swap(str_t str, str_t swap)
{
    STR temp;

    /* validity check */
    if (!str) return;
    if (!swap) return;

    temp = *str;
    *str = *swap;
    *swap = temp;
}

int str_copy(str_t str, int pos, int len, char *buf)
{
    int length = 0;

    /* validity check */
    if (!str) return 0;
    if (pos < 0 || pos > str->parent.size) return 0;

    /* calculate the actual length of be replaced */
    length = len;
    if (length > str->parent.size - pos) length = str->parent.size - pos;

    /* copy str char to buffer */
    memcpy(buf, &((char *)(str->parent.base))[pos], length);

    return length;
}

static int get_digit(const char *s, int *out_digit)
{
    int digit = 0;
    int len = 0;
    while (is_digit(*s))
    {
        digit = digit * 10 + *(s++) - '0';
        len++;
    }
    *out_digit = digit;
    return len;
}

str_t str_format(str_t str, const char *format, ...)
{
    va_list args;                       /* variable parameter */
    int len = 0;                        /* length of sub string */
    char *s = NULL;                     /* temp string */
    str_info_t info;                    /* str info */
    double dbl = 0.0;                   /* double precision floating point */
    const char *begin = NULL;           /* begin of format */
    char qualifier = 0;                 /* conversion qualifier for integer as 'h','l','L' */
    int width = 0;                      /* transition field width */
    int precision = 0;                  /* minimum digits of integers and maximum digits of strings */
    char tfmt[16] = {'%',0};            /* temporary format */

    /* validity check */
    if (!str) return NULL;

    /* reset str */
    str_assign(str, "");

    /* accept uncertain parameters */
    va_start(args, format);

    for (; *format; format++)
    {
        /* normal char */
        if (*format != '%')
        {
            if (!begin) begin = format;
            continue;
        }

        /* copy normal string to str */
        if (begin)
        {
            len = format - begin;

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            while (len--)
            {
                ((char *)(str->parent.base))[str->parent.size++] = *begin++;
            }

            begin = NULL;
        }

        begin = format;

        /* skip flags */
        while (1)
        {
            /* skips the first '%' also */
            format++;
            if ((*format != '0') &&
                (*format != ' ') && 
                (*format != '+') && 
                (*format != '-') && 
                (*format != '#')) break;
        }

        /* get field width */
        width = -1;
        if (is_digit(*format)) 
        {
            format += get_digit(format, &width);
        }
        else if (*format == '*')
        {
            format++;
            /* it's the next argument */
            width = va_arg(args, int);
            if (width < 0)
            {
                width = -width;
            }
        }

        /* get the precision */
        precision = -1;
        if (*format == '.')
        {
            format++;
            if (is_digit(*format)) 
            {
                format += get_digit(format, &precision);
            }
            else if (*format == '*')
            {
                format++;
                /* it's the next argument */
                precision = va_arg(args, int);
            }
            if (precision < 0) precision = 0;
        }

        /* get the conversion qualifier */
        qualifier = 0;
        if (*format == 'h' || *format == 'l' || *format == 'L')
        {
            qualifier = *format;
            format++;
            if (qualifier == 'l' && *format == 'l')
            {
                qualifier = 'L';
                format++;
            }
        }

        /* format distribution */
        switch (*format)
        {
        case 'c':
            len = MAX(width, 1);

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            /* generate temporary format */
            memcpy(&tfmt[1], begin + 1, format - begin);
            tfmt[format - begin + 1] = 0;
            begin = NULL;

            /* format conversion */
            len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, va_arg(args, int));
            if (len > 0) str->parent.size += len;

            break;

        case 's':
            /* get string address */
            info = string_info(va_arg(args, void*));

            /* get string info */
            s = info.base;
            len = info.length;

            if (precision > 0 && len > precision) len = precision;
            len = MAX(width, len);

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            /* generate temporary format */
            memcpy(&tfmt[1], begin + 1, format - begin);
            tfmt[format - begin + 1] = 0;
            begin = NULL;

            /* format conversion */
            len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, s);
            if (len > 0) str->parent.size += len;

            break;

        case 'p':
            /* if no width is specified, the default width will be used */
            if (width == -1) width = sizeof(void*) * 2;

            len = width;

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            /* generate temporary format */
            memcpy(&tfmt[1], begin + 1, format - begin);
            tfmt[format - begin + 1] = 0;
            begin = NULL;

            /* format conversion */
            len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, (long)va_arg(args, void*));
            if (len > 0) str->parent.size += len;

            break;

            /* float number formats */
        case 'a':
        case 'A':
        case 'e':
        case 'E':
        case 'g':
        case 'G':
        case 'f':
            dbl = va_arg(args, double);

            if (*format == 'f')
            {
                /* get the gradient length of the integer part of the floating point number */
                if (fabs(dbl) < powl(10, 8)) len = 8;
                else if (fabs(dbl) < powl(10, 16)) len = 16;
                else if (fabs(dbl) < powl(10, 32)) len = 32;
                else if (fabs(dbl) < powl(10, 64)) len = 64;
                else if (fabs(dbl) < powl(10, 128)) len = 128;
                else if (fabs(dbl) < powl(10, 256)) len = 256;
                else len = 309;
            }
            else
            {
                len = 24;
            }

            len += ((precision==-1?6:precision) + 1);
            len = MAX(width, len);

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            /* generate temporary format */
            memcpy(&tfmt[1], begin + 1, format - begin);
            tfmt[format - begin + 1] = 0;
            begin = NULL;

            /* format conversion */
            len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, dbl);
            if (len > 0) str->parent.size += len;

            break;

            /* integer number formats */
        case 'o':
        case 'X':
        case 'x':
        case 'd':
        case 'i':
        case 'u':
            len = qualifier=='L'?32:16;
            len = MAX(width, len);

            /* capacity expansion */
            if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

            /* generate temporary format */
            memcpy(&tfmt[1], begin + 1, format - begin);
            tfmt[format - begin + 1] = 0;
            begin = NULL;

            /* format conversion */
            if (qualifier == 'L')       len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, va_arg(args, long long));
            else if (qualifier == 'l')  len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, va_arg(args, int));
            else if (qualifier == 'h')  len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, (short)va_arg(args, int));
            else                        len = sprintf(&((char *)(str->parent.base))[str->parent.size], tfmt, (int)va_arg(args, int));
            if (len > 0) str->parent.size += len;

            break;

        case '%':
            /* push % */
            str_push_back(str, '%');
            begin = NULL;
            break;

        default:
            /* push % */
            str_push_back(str, '%');
            begin = NULL;

            /* push normal */
            if (*format) str_push_back(str, *format);
            else format--;
            break;
        }
    }

    /* copy tail string to str */
    if (begin)
    {
        len = format - begin;

        /* capacity expansion */
        if (!str_alter_capacity(str, str->parent.size + len)) goto FAIL;

        while (len--)
        {
            ((char *)(str->parent.base))[str->parent.size++] = *begin++;
        }
    }

    /* confirm capacity */
    if (!str_alter_capacity(str, str->parent.size)) goto FAIL;

    /* add end symbol */
    ((char *)(str->parent.base))[str->parent.size] = '\0';

    va_end(args);
    
    return str;

FAIL:
    ((char *)(str->parent.base))[str->parent.size] = '\0';
    va_end(args);
    return NULL;
}

void* str_iterator_begin(vobject_t object)
{
    if (!object) return NULL;
    return ((str_t)object)->parent.base;
}

void* str_iterator_end(vobject_t object)
{
    if (!object) return NULL;
    return &(((char *)(((str_t)object)->parent.base))[((str_t)object)->parent.size - 1]);
}

void* str_iterator_move(vobject_t object, void *node, int step)
{
    char *temp = ((char *)node) + step;
    return (void *)temp;
}

void str_iterator_nfs(void *node, void **first, void **second)
{
    if (!node) return;
    if (first) *first = NULL;
    if (second) *second = node;
}
